package com.iot08.api.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.iot08.api.config.redis.RedisClusterService;
import com.iot08.api.config.redis.RedisVideoService;
import com.iot08.api.service.supplement.KafkaService;
import com.iot08.api.service.supplement.TCPService;
import com.iot08.api.utils.sw.RtcTokenBuilder;
import com.iot08.common.constant.*;
import com.iot08.api.vo.request.RejectDeviceVideoReqVO;
import com.iot08.api.vo.request.SendVideoReqVO;
import com.iot08.common.entity.app.*;
import com.iot08.common.service.base.BaseService;
import com.iot08.common.utils.*;
import com.iot08.common.utils.response.ResponseEnum;
import com.iot08.common.utils.response.ResponseMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.jacoco.agent.rt.internal_43f5073.core.internal.flow.IFrame;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.nio.charset.Charset;
import java.util.*;

/**
 * @version 1.0
 * @Description:
 * @author: Rock
 * @time: 2020/12/25 15:51
 */
@Slf4j
@Service
public class DeviceVideoService extends BaseService<DeviceVideo> {

    @Resource
    private DeviceService deviceService;
    @Resource
    private VideoModelService videoModelService;
    @Resource
    private ModelsService modelsService;

    @Resource
    private ChatGroupService chatGroupService;
    //视频
    @Resource
    private RedisVideoService redisVideoService;

    @Resource
    private KafkaService kafkaService;
    @Resource
    private DeviceLastMissedVideoCallService deviceLastMissedVideoCallService;

    //视通
    @Value("${vm.app.key}")
    private String vmAppKey;
    @Value("${vm.app.connection.address}")
    private String vmAppConnectionAddress;
    @Value("${vm.watch.connection.address}")
    private String vmWatchConnectionAddress;

    //声网
    @Value("${sw.watch.appid}")
    private String swAPP_ID;
    @Value("${sw.watch.customer_id}")
    private String swCUSTOMER_ID;
    @Value("${sw.watch.customer_certificate}")
    private String swCUSTOMER_CERTIFICATE;
    @Value("${sw.watch.app_certificate}")
    private String swAPP_CERTIFICATE;

    //菊风

    @Value("${jf.sjVideo.app.key:}")
    private String jf_SJAppKey;

    @Value("${jf.sjVideo.ASRWatch.secret_key:}")
    private String jf_SJASRWatchSecretKey;

    @Value("${jf.app.key}")
    private String jfAppKey;

    @Value("${jf.ASRWatch.secret_key}")
    private String jfASRWatchSecretKey;
    @Value("${jf.app.connection.address}")
    private String jfAppConnectionAddress;
    @Value("${jf.watch.connection.address}")
    private String jfWatchConnectionAddress;


    // 单次通话时长/秒 设备小热的等待时间 (限制一次拨打的时长)
    @Value("${video.LIMIT_ONE_CALL_VIDEO_TIME}")
    private int LIMIT_ONE_CALL_VIDEO_TIME;

    // 不启用限制的 间隔总分钟数 (超过该分钟数就不启用限制,并清除之前的限制) /秒
    @Value("${video.LIMIT_INTERVAL_VIDEO_TIME}")
    private int LIMIT_INTERVAL_VIDEO_TIME;

    // 启用限制的 视频通话总分钟数 最近视频通话累计时间 /秒
    @Value("${video.LIMIT_ALL_VIDEO_TIME}")
    private int LIMIT_ALL_VIDEO_TIME;

    // 拨打视频等待时间 /秒
    @Value("${video.VIDEO_WAIT_TIME}")
    private int VIDEO_WAIT_TIME;


    /**
     * 添加一条视频记录
     *
     * @param devVideoType 设备的视频类型 SW:声网  JF:菊风  VM:视通
     * @param imei
     * @param videoId
     * @param type
     * @param openId
     * @param platform
     * @param isDevToDev   是否是 设备拨打设备
     * @param callType     通话类型 0:视频 1:语音
     * @time: 2021/1/18 9:48
     * @author: Rock
     * @version 1.0
     */
    public void add(String devVideoType, String imei, String videoId, String type, String openId, String platform, Boolean isDevToDev, Integer callType) {
        DeviceVideo deviceVideo = new DeviceVideo();
        deviceVideo.setDevVideoType(devVideoType);
        deviceVideo.setImei(imei);
        //存的是通话唯一标识
        deviceVideo.setVideoId(videoId);
        deviceVideo.setStatus(0);
        deviceVideo.setStartTime(DateUtil.nowTimestampToDouble());
        deviceVideo.setRequestType(type);
        deviceVideo.setOpenid(openId);
        deviceVideo.setCallType(callType);
        deviceVideo.setExpiryAt(DateUtil.getSystemExpiryAt());
        if (StringUtil.isNotBlank(platform)) {
            deviceVideo.setPlatform(platform);
        }
        if (isDevToDev) {
            deviceVideo.setIsDevToDev(1);
        }
        //保存记录
        this.save(deviceVideo);
    }

    /**
     * @param videoId
     * @time: 2021/3/25 11:46
     * @author: Rock
     * @version 1.0
     */
    public DeviceVideo getByVideoId(String videoId) {
        return this.findOne(new Query(Criteria.where("video_id").is(videoId)));
    }

    /**
     * @param device
     * @param type
     * @param deviceVideoType
     * @param openId          APP<==>设备,为openid 设备<==>设备 为imei号
     * @param ident           设备端发起才有, 否则为null
     * @param isVideoMonito   //0=非监控, 1=监控
     * @param tempData
     * @param updata          设备端发起才有, 否则为null
     * @param callType        通话类型: 0:视频通话, 1:语音通话
     * @time: 2021-10-26 16:06
     * @author: Weichao
     * @version 1.0
     */
    public int invitateVideo(Device device,
                             String type, String deviceVideoType,
                             String openId, Object ident,
                             Integer isVideoMonito, Map<String, Object> tempData,
                             JSONObject updata, Integer callType, Integer isGroup, JSONObject emitDeviceDemandData, int version) {


        // 是否是设备对设备
        boolean isDevToDevVideo = false;
        // 设备拨打设备时的 对方设备的账号
        String otherDevImei = null;
        Device otherDev = null;
        boolean isGroupChat = false;


        if (updata != null) {
            String devToDev = updata.getString("devToDev");
            if (devToDev != null && Integer.valueOf(devToDev) == 1) {
                isDevToDevVideo = true;
                // 设备拨打设备时 openId 为要拨打方的imei
                otherDevImei = openId;
                otherDev = deviceService.findById(otherDevImei);
            }
        }

        // 检查是否为群聊（用isGroup用来区分）
        //判断是新旧协议
        //Integer isNewWechatProtocol = device.getIsNewWechatProtocol();
//        if (isNewWechatProtocol == 1 && updata != null) {
//            isGroupChat = updata.getString("x").equals("1");
//        }else if (isNewWechatProtocol == 1 && updata == null) {
//            isGroupChat = isGroup.equals(1);
//        }
        if (updata != null) {
            isGroupChat = updata.getString("isGroup").equals("1");
        } else {
            if (isGroup == null) {
                isGroup = 0;
            }
            isGroupChat = isGroup.equals(1);
        }
        if (callType == null) {
            callType = 0;
        }


        // 通用视频配置和限制检查
        JSONObject videoConfig = getVideoConfig(device.getModelId());
        /*
        // 如果是语音,总通话时长可改成 1小时
        if(callType == 1){
            oneCallTime = 60 * 60;
        }
        */

        log.info("imei:{} 视频配置={}", device.getId(), videoConfig.toJSONString());
        Long nowTime = DateUtil.nowTimestampToLong();
        Integer oneCallTime = videoConfig.getInteger("one_call_time");
        Integer callInterval = videoConfig.getInteger("call_interval");

        // 553视频限制检查（通用逻辑）
        int limitCheckResult = checkVideoLimit(device, callType, nowTime, callInterval);
        if (limitCheckResult != 0) {
            return limitCheckResult;
        }


        // 在线状态检查（通用逻辑）
        String onlineStatus = checkOnlineStatus(device, type, isVideoMonito);
        if (!"1".equals(onlineStatus)) {
            log.error("设备未联网,可能当前信号差或关机,imei={}", device.getId());
            return 0;
        }

        // 根据聊天类型处理
        if (isGroupChat) {
            return handleGroupChatVideo(device, type, deviceVideoType, openId, ident,
                    isVideoMonito, tempData, updata, callType,
                    emitDeviceDemandData, videoConfig, oneCallTime);
        } else {
            return handleSingleChatVideo(device, type, deviceVideoType, openId, ident,
                    isVideoMonito, tempData, updata, callType,
                    emitDeviceDemandData, videoConfig, oneCallTime,
                    isDevToDevVideo, otherDevImei, otherDev, version);
        }


    }

    /**
     * 处理群聊视频通话
     */
    private int handleGroupChatVideo(Device device, String type, String deviceVideoType, String openId,
                                     Object ident, Integer isVideoMonito, Map<String, Object> tempData,
                                     JSONObject updata, Integer callType, JSONObject emitDeviceDemandData,
                                     JSONObject videoConfig, Integer oneCallTime) {
        // 获取群组信息
        ChatGroup chatGroup = chatGroupService.getChatGroupByDevice(device);
        log.info("群组信息={}", chatGroup);

        //  执行前置检查
        int checkResult = checkGroupVideoCallPreConditions(chatGroup);
        if (checkResult != 0) {
            return checkResult;
        }

        // 根据发起类型确定要排除的ID
        String excludeId;
        if (ConstKafka.DEVICE_TYPE_DownVideoCallWithWatch.equals(type)) {
            // APP发起，排除APP用户
            excludeId = openId;
        } else {
            // 设备发起，排除设备
            excludeId = device.getId();
        }

        // 获取可用成员列表
        Map<String, List<String>> availableMembers = getAvailableGroupMembers(chatGroup, excludeId);

        // 从updata中获取参数,设备打过来的
//        String videoId = updata != null ? updata.getString("videoId") : null; // 房间号
//        String vid = updata != null ? updata.getString("video_identify") : null; // 会话唯一标识

        String shortId = RedisClusterService.getJedisCluster().hget(device.getId(), "shortId");
        String video_identify = shortId + "_" + DateUtil.dateToStr(new Date(), DateUtil.FORMAT_6);
        String vid = video_identify;
        String newVideoId = shortId + "_" + "group";
        if (StringUtils.isNotBlank(shortId) && updata != null) {
            String videoId = updata.getString("videoId");
            video_identify = updata.getString("video_identify");
            if (StringUtil.isNotBlank(videoId)) {
                vid = video_identify; // 使用设备传来的video_identify作为vid
                newVideoId = videoId;
            }
        }


        // 设置群聊标识
        String groupId = chatGroup.getId();
        //有状态不用改
        if (redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + groupId) == null) {
            deviceService.updateById(device.getId(), new Update().set("group_video_identify", video_identify));
            //  deviceService.updateById(device.getId(), new Update().set("newVideo_id", video_identify));
        }


        // 设置群组和成员的通话状态（但不设置发起者的状态）
        setGroupCallStatus(groupId, video_identify, availableMembers);


        //  单独设置发起者的通话状态
        redisVideoService.setex(CacheEnum.GROUP_VIDEO.getKey() + excludeId,
                CacheEnum.GROUP_VIDEO.getExpire(), groupId);

//
//        // 设置通话状态
//        redisVideoService.setex(CacheEnum.VIDEO.getKey() + device.getId(), CacheEnum.VIDEO.getExpire(), groupId);

//        // 配置视频参数 - 注意这里传入的是vid(即video_identify)
//        configureVideoParameters(device, type, deviceVideoType, groupId, vid, oneCallTime,
//                emitDeviceDemandData, tempData, videoConfig, updata);


        // 保存通话记录
        saveCallRecord(device, type, deviceVideoType, video_identify, openId, false, callType);

        deviceService.updateById(device.getId(), new Update().set("newVideo_id", video_identify));


        // ========== APP发起群视频的推送逻辑 ==========
        if (ConstKafka.DEVICE_TYPE_DownVideoCallWithWatch.equals(type)) {
            // 构建群视频通用推送参数
            JSONObject basePushData = new JSONObject();
            // basePushData.putAll(tempData);
            basePushData.put("type", "videoGroup");
            basePushData.put("isGroup", 1);  // 群聊标识
            //basePushData.put("chatType", "group");
            basePushData.put("groupId", groupId);
            //  basePushData.put("groupName", chatGroup.getName());
            basePushData.put("user_openid", "00000000000");
            basePushData.put("call_type", callType);
            //  basePushData.put("isMonito", isVideoMonito);
            // 设置消息标题
            // 获取发起者（APP用户）的信息
            Account initiatorAccount = accountService.findById(openId);
            String initiatorName = initiatorAccount != null && StringUtil.isNotBlank(initiatorAccount.getName())
                    ? initiatorAccount.getName() : device.getName() + "的" + getUserDeviceRemark(openId, device.getId());
            log.info("群视频设备推送处理 - 获取发起者（APP用户）的信息：{}", initiatorName);

            String msgTitleKey = callType == 0 ? "API.pushTitle.video.invitationGroupVideo" : "API.pushTitle.video.invitationAudio";
            basePushData.put("msgTitle", MessageUtils.getLocale(device.getLocale(), msgTitleKey, initiatorName));
            emitDeviceDemandData.put("call_type", callType);
            // 执行推送 - 传入所有必要参数
            performGroupVideoPush(device, openId, availableMembers, basePushData,
                    emitDeviceDemandData, type, deviceVideoType, vid, oneCallTime,
                    videoConfig, updata, tempData, isVideoMonito, ident, initiatorName, video_identify, newVideoId);
        } else {
            // 设备发起的群视频
            handleDeviceInitiatedGroupVideo(device, chatGroup, availableMembers,
                    emitDeviceDemandData, type, deviceVideoType, vid, oneCallTime,
                    videoConfig, updata, callType, tempData, video_identify, newVideoId);


        }


        return 999;

    }

    /**
     * 群视频设备推送处理
     */
    private void handleDeviceInitiatedGroupVideo(Device device, ChatGroup chatGroup,
                                                 Map<String, List<String>> availableMembers,
                                                 JSONObject emitDeviceDemandData, String type, String deviceVideoType,
                                                 String vid, Integer oneCallTime, JSONObject videoConfig, JSONObject updata,
                                                 Integer callType, Map<String, Object> tempData, String video_identify, String newVideoId) {
        List<String> availableDevices = availableMembers.get("availableDevices");
        List<String> availableAppMembers = availableMembers.get("availableMembers");

        // 基础推送数据
        JSONObject basePushData = new JSONObject();
        basePushData.put("type", "videoGroup");
        basePushData.put("isGroup", 1);
        //basePushData.put("chatType", "group");
        basePushData.put("groupId", chatGroup.getId());
        // basePushData.put("groupName", chatGroup.getName());
        basePushData.put("call_type", callType);
        // basePushData.put("isMonito", 0);
        // basePushData.put("imei", device.getId());
        // basePushData.put("name", deviceService.getDeviceName(device));

        String msgTitleKey = callType == 0 ?
                "API.pushTitle.video.invitationVideo" :
                "API.pushTitle.video.invitationAudio";
        basePushData.put("msgTitle", MessageUtils.getLocale(device.getLocale(),
                msgTitleKey, device.getName()));

        // 1. 通知所有可用的APP成员
        if (availableAppMembers != null) {
            for (String memberOpenId : availableAppMembers) {
                try {
                    // 直接给设备名字
                    // String deviceRemark = getUserDeviceRemark(memberOpenId, device.getId());
                    //log.info("群视频设备推送处理 - 获取该APP用户对设备的备注：{}", deviceRemark);
                    //if (StringUtil.isBlank(deviceRemark)) {
                    String deviceRemark = device.getName(); // 备注，使用设备默认名称
                    //}
                    tempData.put("initiator", deviceRemark);
                    // 为每个APP成员创建独立的配置
                    //Map<String, Object> memberTempData = new HashMap<>();
                    JSONObject memberEmitData = new JSONObject();

                    // 为该成员配置视频参数
                    configureVideoParameters(device, type, deviceVideoType, null, vid,
                            oneCallTime, memberEmitData, tempData, videoConfig, updata, video_identify, newVideoId, 1);

                    JSONObject memberPush = new JSONObject();
                    memberPush.putAll(basePushData);
                    memberPush.putAll(tempData);
                    memberPush.put("openid", memberOpenId);

                    JSONObject dataPush = new JSONObject();
                    dataPush.put("type", ConstKafka.ACCOUNT_MESSAGE);
                    dataPush.put("message", memberPush);

                    kafkaService.sendPushToAccount(ConstKafka.ACCOUNT_TYPE_3,
                            memberOpenId, dataPush);
                    log.info("设备群视频：推送给成员 {} 成功", memberOpenId);
                } catch (Exception e) {
                    log.error("设备群视频：推送给成员 {} 失败", memberOpenId, e);
                }
            }
        }


    }

    /**
     * 获取用户对设备的备注（从Account的deviceMap中获取）
     *
     * @param openId   用户的openId
     * @param deviceId 设备ID
     * @return 用户对设备的备注
     */
    public String getUserDeviceRemark(String openId, String deviceId) {
        Account account = accountService.findById(openId);
        if (account == null || account.getDeviceMap() == null) {
            return null;
        }

        Map<String, Object> deviceInfo = account.getDeviceMap().get(deviceId);
        if (deviceInfo != null) {
            Object relationship = deviceInfo.get("relationship");
            if (relationship != null && StringUtil.isNotBlank(relationship.toString())) {
                return relationship.toString();
            }
        }
        return null;
    }

    /**
     * 获取设备对用户的备注（从Device的userMap中获取）
     *
     * @param device 设备对象
     * @param openId 用户openId
     * @return 设备视角下的用户备注
     */
    private String getDeviceUserRemark(Device device, String openId) {
        if (device == null || device.getUserMap() == null) {
            return null;
        }

        Map<String, Object> userInfo = device.getUserMap().get(openId);
        if (userInfo != null) {
            Object relationship = userInfo.get("relationship");
            if (relationship != null && StringUtil.isNotBlank(relationship.toString())) {
                return relationship.toString();
            }
        }
        return null;
    }

    /**
     * 执行群视频推送
     *
     * @param device               发起设备
     * @param chatGroup            群组对象
     * @param openId               发起者openId
     * @param availableMembers     可用成员列表
     * @param basePushData         基础推送数据
     * @param emitDeviceDemandData 设备推送数据
     */

    @Resource
    private AccountService accountService;

    private void performGroupVideoPush(Device device, String openId,
                                       Map<String, List<String>> availableMembers, JSONObject basePushData,
                                       JSONObject emitDeviceDemandData, String type, String deviceVideoType,
                                       String vid, Integer oneCallTime, JSONObject videoConfig,
                                       JSONObject updata, Map<String, Object> tempData, Integer isVideoMonito, Object ident, String initiatorName, String video_identify,
                                       String newVideoId) {

        List<String> availableDevices = availableMembers.get("availableDevices");
        List<String> availableAppMembers = availableMembers.get("availableMembers");


        // 1. 推送给可用的APP成员
        if (availableAppMembers != null) {
            for (String memberOpenId : availableAppMembers) {
                try {
                    // 为每个APP成员创建独立的配置
                    //Map<String, Object> memberTempData = new HashMap<>();
                    JSONObject memberEmitData = new JSONObject();

                    // 为该成员配置视频参数
                    configureVideoParameters(device, type, deviceVideoType, memberOpenId, vid,
                            oneCallTime, memberEmitData, tempData, videoConfig, updata, video_identify, newVideoId, 1);
                    tempData.put("initiator", initiatorName);


                    // 合并基础推送数据和配置数据
                    JSONObject memberPush = new JSONObject();
                    memberPush.putAll(basePushData);
                    memberPush.putAll(tempData);
                    memberPush.put("openid", memberOpenId);

                    JSONObject dataPush = new JSONObject();
                    dataPush.put("type", ConstKafka.ACCOUNT_MESSAGE);
                    dataPush.put("message", memberPush);

                    kafkaService.sendPushToAccount(ConstKafka.ACCOUNT_TYPE_3, memberOpenId, dataPush);
                    log.info("APP群视频：推送给成员 {} 成功，配置：{}", memberOpenId, tempData);
                } catch (Exception e) {
                    log.error("APP群视频：推送给成员 {} 失败", memberOpenId, e);
                }
            }
        }

        // 2. 推送给设备（包括发起设备）
//        String appUserRemark = getDeviceUserRemark(device, openId);
//        log.info("群视频设备推送处理 - 获取设备对APP用户的备注：{}", appUserRemark);
//        if (StringUtil.isBlank(appUserRemark)) {
//            appUserRemark = initiatorName; // 如果没有备注，使用用户昵称
//        }

        //  emitDeviceDemandData.put("initiator", appUserRemark);
        configureVideoParameters(device, type, null, device.getId(),
                vid, oneCallTime, emitDeviceDemandData, tempData, videoConfig, updata, video_identify, newVideoId, 1);
        // emitDeviceDemandData.put("video_identify", vid);
        emitDeviceDemandData.put("initiator", initiatorName);
        emitDeviceDemandData.put("isMonito", isVideoMonito);
        emitDeviceDemandData.put("ident", ident);
        emitDeviceDemandData.put("isGroup", 1);
        emitDeviceDemandData.put("app_username", openId);
        kafkaService.emitDeviceDemand(device.getId(), ConstKafka.DEVICE_TYPE_DownVideoCallWithWatch, emitDeviceDemandData, openId);


    }

    /**
     * 设置群组通话状态
     *
     * @param groupId          群组ID
     * @param videoIdentify    视频标识
     * @param availableMembers 可用成员Map（从getAvailableGroupMembers返回的结果）
     */
    private void setGroupCallStatus(String groupId, String videoIdentify, Map<String, List<String>> availableMembers) {
        // 1. 设置群组通话状态
        redisVideoService.setex(CacheEnum.GROUP_VIDEO.getKey() + groupId,
                CacheEnum.GROUP_VIDEO.getExpire(), videoIdentify);

        // 2. 设置可用设备的通话状态
        //  List<String> availableDevices = availableMembers.get("availableDevices");
//        if (availableDevices != null) {
//            for (String deviceImei : availableDevices) {
//                redisVideoService.setex(CacheEnum.GROUP_VIDEO.getKey() + deviceImei,
//                        CacheEnum.GROUP_VIDEO.getExpire(), groupId);
//            }
//        }

        // 3. 设置可用APP成员的通话状态
//        List<String> availableAppMembers = availableMembers.get("availableMembers");
//        if (availableAppMembers != null) {
//            for (String memberOpenId : availableAppMembers) {
//                redisVideoService.setex(CacheEnum.GROUP_VIDEO.getKey() + memberOpenId,
//                        CacheEnum.GROUP_VIDEO.getExpire(), groupId);
//            }
//        }
//
//        log.info("设置群组 {} 通话状态完成，设备{}个，APP成员{}个",
//                groupId,
//                availableDevices != null ? availableDevices.size() : 0,
//                availableAppMembers != null ? availableAppMembers.size() : 0);
    }


    /**
     * 获取可用的群组成员
     *
     * @param chatGroup 群组对象
     * @param excludeId 需要排除的ID（发起者）
     * @return Map包含 "availableDevices" 和 "availableMembers" 两个List
     */
    public Map<String, List<String>> getAvailableGroupMembers(ChatGroup chatGroup, String excludeId) {
        Map<String, List<String>> result = new HashMap<>();
        List<String> availableDevices = new ArrayList<>();
        List<String> availableMembers = new ArrayList<>();

        // 1. 检查设备
        if (CollectionUtil.isNotEmpty(chatGroup.getDeviceMap())) {
            for (String deviceImei : chatGroup.getDeviceMap().keySet()) {
                if (deviceImei.equals(excludeId)) {
                    continue;
                }

                // 检查设备是否存在
                Device memberDevice = deviceService.findById(deviceImei);
                if (memberDevice == null) {
                    log.warn("群组成员设备 {} 不存在", deviceImei);
                    continue;
                }

//                // 检查设备通话状态
//                String deviceCallStatus = redisVideoService.get(CacheEnum.VIDEO.getKey() + deviceImei);
//                if (StringUtil.isBlank(deviceCallStatus)) {
//                    availableDevices.add(deviceImei);
//                } else {
//                    log.info("设备 {} 正在通话中，跳过", deviceImei);
//                }
                // 直接添加到可用设备列表（不检查通话状态）
                availableDevices.add(deviceImei);
            }
        }

        // 2. 检查APP成员
        if (CollectionUtil.isNotEmpty(chatGroup.getMemberMap())) {
            for (String memberOpenId : chatGroup.getMemberMap().keySet()) {
                if (memberOpenId.equals(excludeId)) {
                    continue;
                }

//                // 检查成员通话状态
//                String memberCallStatus = redisVideoService.get(CacheEnum.VIDEO.getKey() + memberOpenId);
//                if (StringUtil.isBlank(memberCallStatus)) {
//                    availableMembers.add(memberOpenId);
//                } else {
//                    log.info("成员 {} 正在通话中，跳过", memberOpenId);
//                }
                // 直接添加到可用成员列表（不检查通话状态）
                availableMembers.add(memberOpenId);
            }
        }

        result.put("availableDevices", availableDevices);
        result.put("availableMembers", availableMembers);
        return result;
    }


    /**
     * 检查群聊视频通话前置条件
     *
     * @param chatGroup 群组对象
     * @return 0-检查通过，其他值表示错误码
     */
    private int checkGroupVideoCallPreConditions(ChatGroup chatGroup) {
        if (chatGroup == null) {
            log.error("群组不存在");
            return -5; // 无效用户
        }

        // 检查群组是否正在通话
//        String groupCallStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + chatGroup.getId());
//        if (StringUtil.isNotBlank(groupCallStatus)) {
//            log.info("群组 {} 正在通话中", chatGroup.getId());
//            return 3; // 对方正在通话中,请稍后重试...
//        }

        return 0; // 检查通过

    }


    /**
     * 处理单聊视频通话
     */
    private int handleSingleChatVideo(Device device, String type, String deviceVideoType, String openId, Object ident, Integer isVideoMonito,
                                      Map<String, Object> tempData,
                                      JSONObject updata, Integer callType, JSONObject emitDeviceDemandData,
                                      JSONObject videoConfig, Integer oneCallTime,
                                      boolean isDevToDevVideo, String otherDevImei, Device otherDev, int version) {
        //新的视频通话ID
        // String vid = device.getId() + "-" + DateUtil.dateToStr(new Date(), DateUtil.FORMAT_6);
        // 在handleSingleChatVideo开始就处理video_identify逻辑
        String shortId = RedisClusterService.getJedisCluster().hget(device.getId(), "shortId");
        String video_identify = shortId + "_" + DateUtil.dateToStr(new Date(), DateUtil.FORMAT_6);
        String vid = video_identify;
        String newVideoId = shortId + "_" + openId;
        if (StringUtils.isNotBlank(shortId) && updata != null) {
            String videoId = updata.getString("videoId");
            if (StringUtil.isNotBlank(videoId)) {
                //新版本用videoId作为videoId
                newVideoId = videoId;
                video_identify = updata.getString("video_identify");
                //老版本用时间戳作为videoId
                vid = video_identify;
            }
        }
        if (!isDevToDevVideo) {
            //不是设备与设备视频时才进行 视频联系人的openid是否存在的判断
            if (CollectionUtil.isNotEmpty(device.getCheckedOpenidsMap())) {
                if (!CollectionUtil.containsKey(device.getUserMap(), openId)) {
                    log.info("判断imei:{} 视频通话的联系人是否存在, 不存在", device.getId());
                    return 4;
                }
            } else {
                // TODO 这个是临时的,用python的老方法读取 通讯录里的视频联系人
                Map<String, Map<String, Object>> contactMap = device.getContactMap();
                if (contactMap != null) {
                    boolean exist = false;
                    for (Map<String, Object> item : contactMap.values()) {
                        JSONObject itemMap = new JSONObject(item);
                        Integer canVideo = itemMap.getInteger("canVideo");
                        if (canVideo != null && canVideo == 1 && openId.equals(itemMap.getString("openid"))) {
                            exist = true;
                        }
                    }
                    if (!exist) {
                        log.info("判断imei:{} 视频通话的联系人openid:{}是否存在, 不存在", device.getId(), openId);
                        return 4;
                    }
                }
            }

        } else {
            //设备与设备视频时进行 对方是否在线 和 对方是否可视频 的判断
            if (otherDev.getWatchCustomFunction().contains("DTOD_VIDEO")) {
                String otherDevOnlineStatus = RedisClusterService.getJedisCluster().hget(otherDevImei, ConstCacheField.ONLINE_STATUS);
                if (!"1".equals(otherDevOnlineStatus)) {
                    //好友不在线
                    return 15;
                }
            } else {
                //应该是流程错误, 拨打了无DevToDev视频功能的设备
                return 14;
            }

        }

        //更新设备信息
        if (redisVideoService.get(CacheEnum.VIDEO.getKey() + device.getId()) == null || redisVideoService.get(CacheEnum.VIDEO.getKey() + openId) == null) {
            //  deviceService.updateById(device.getId(), new Update().set("newVideo_id", video_identify));
            deviceService.updateById(device.getId(), new Update().set("video_identify", video_identify));
        }


        // 设置通话状态
        redisVideoService.setex(CacheEnum.VIDEO.getKey() + device.getId(), CacheEnum.VIDEO.getExpire(), openId);
        redisVideoService.setex(CacheEnum.VIDEO.getKey() + openId, CacheEnum.VIDEO.getExpire(), device.getId());

        //  配置视频/语音参数
        configureVideoParameters(device, type, deviceVideoType, openId, vid, oneCallTime,
                emitDeviceDemandData, tempData, videoConfig, updata, video_identify, newVideoId, version);

        // 设置联系人信息
        if (!isDevToDevVideo) {// 不是设备之间的通话
            if (CollectionUtil.containsKey(device.getUserMap(), openId)) {
                emitDeviceDemandData.put("contact", device.getUserMap().get(openId).get("relationship"));
            } else {
                emitDeviceDemandData.put("contact", "mom");
            }
            Map<String, Map<String, Object>> userMap = device.getUserMap();
            JSONObject item = new JSONObject(userMap.get(openId));
            emitDeviceDemandData.put("app_username", openId);
            emitDeviceDemandData.put("relationship", item.getString("relationship"));
            emitDeviceDemandData.put("relationship_image_id", item.getString("relationship_image_id"));
        } else {
            // 是 设备to设备 的 contact 是对端的昵称
            emitDeviceDemandData.put("contact", otherDev.getName());
            String deviceName = deviceService.getDeviceName(otherDev);
            emitDeviceDemandData.put("dev_username", "imei-" + otherDevImei);
            emitDeviceDemandData.put("relationship", deviceName);
            //好友的视频关系为 -1
            emitDeviceDemandData.put("relationship_image_id", -1);
        }
        emitDeviceDemandData.put("isGroup", 0);
        emitDeviceDemandData.put("call_type", callType);

        // 保存通话记录
        saveCallRecord(device, type, deviceVideoType, video_identify, openId, isDevToDevVideo, callType);

        deviceService.updateById(device.getId(), new Update().set("newVideo_id", video_identify));

        // 发送通知给目标设备
        sendVideoNotification(device, type, openId, ident, isVideoMonito, callType,
                deviceVideoType, emitDeviceDemandData, tempData);


        // 如果是设备对设备通话，需要通知对方设备
        if (isDevToDevVideo) {
            notifyOtherDevice(device, otherDevImei, emitDeviceDemandData, deviceVideoType, vid);
            return 998;
        }

        return 999;


    }

    /**
     * 通知其他设备（设备对设备通话）
     */
    private void notifyOtherDevice(Device device, String otherDevImei, JSONObject emitDeviceDemandData, String deviceVideoType, String vid) {
        //如果是设备对设备的通话的
        //上面已经下发回复了发起视频通话的表 这里复用map, 复制一下修改就行, 下发给要对拨的表
        JSONObject otherEmitDeviceDemandData = new JSONObject();
        otherEmitDeviceDemandData.putAll(emitDeviceDemandData);
        otherEmitDeviceDemandData.put("type", ConstKafka.DEVICE_TYPE_DownVideoCallWithWatch);
        if (device.getModules().contains(ConstVideo.V0VM)) {
            otherEmitDeviceDemandData.put("dev_username", device.getId());
            otherEmitDeviceDemandData.put("username", otherDevImei);
            otherEmitDeviceDemandData.put("password", otherDevImei.substring(otherDevImei.length() - 6));

        } else {
            //TODO 其他暂时只有声网
            //声网需要把token 和 uid 转换一下再发出去
            RtcTokenBuilder tokenBuilder = new RtcTokenBuilder();
            String appToken = tokenBuilder.buildTokenWithUid(device.getId(), 2222, swAPP_ID, swAPP_CERTIFICATE);

            otherEmitDeviceDemandData.put("uid", 2222);
            otherEmitDeviceDemandData.put("app_uid", 1111);
            otherEmitDeviceDemandData.put("token", appToken);

        }
        //什么类型的视频
        otherEmitDeviceDemandData.put("deviceVideoType", deviceVideoType);
        //找不到昵称默认使用宝贝
        otherEmitDeviceDemandData.put("contact", StringUtil.isBlank(device.getName()) ? "宝贝" : device.getName());
        //保持和 APP 视频时一致, 好友视频不会有监控 所以都是 0
        otherEmitDeviceDemandData.put("isMonito", 0);
        //发给要对拨的另一个表
        kafkaService.emitDeviceDemand(otherDevImei, otherEmitDeviceDemandData.getString("type"), otherEmitDeviceDemandData, null);

    }

    /**
     * 发送视频通知
     */
    private void sendVideoNotification(Device device, String type, String openId, Object ident, Integer isVideoMonito, Integer callType, String deviceVideoType, JSONObject emitDeviceDemandData, Map<String, Object> tempData) {
        if (StringUtil.isNotBlank(ident)) {
            emitDeviceDemandData.put("ident", ident);
        }
        if (ConstKafka.DEVICE_TYPE_DownVideoCallWithWatch.equals(type)) {
            emitDeviceDemandData.remove("video_type");
            emitDeviceDemandData.put("isMonito", isVideoMonito);
        }

        //什么类型的视频
        emitDeviceDemandData.put("deviceVideoType", deviceVideoType);

        //手表上发请求视频通话
        if (ConstKafka.DEVICE_TYPE_UpVideoCallWithAPP.equals(type)) {
            //手表上发请求视频通话的
            emitDeviceDemandData.put("type", ConstKafka.DEVICE_TYPE_DownVideoCallWithAPPInfo);
            kafkaService.emitDeviceDemand(device.getId(), ConstKafka.DEVICE_TYPE_DownVideoCallWithAPPInfo, emitDeviceDemandData, openId);

        } else {
            kafkaService.emitDeviceDemand(device.getId(), type, emitDeviceDemandData, openId);
        }

        if (StringUtil.isNotBlank(device.getPlatform())) {
            tempData.put("platform", device.getPlatform());
        }
    }


    /**
     * 保存通话记录
     */
    private void saveCallRecord(Device device, String type, String deviceVideoType, String vid, String openId, boolean isDevToDevVideo, Integer callType) {
        if (ConstKafka.DEVICE_TYPE_DownVideoCallWithWatch.equals(type)) {
            //如果是由APP 拨打的openid 存入redis
            RedisClusterService.getJedisCluster().hset(device.getId(), ConstCacheField.VIDEO_OPENID, openId);
            //保存一下通话记录
            add(deviceVideoType, device.getId(), vid, type, openId, device.getPlatform(), isDevToDevVideo, callType);
            log.info("用户 openid: {} 发起通话请求 成功. type = {}", openId, type);
        } else {
            if (!isDevToDevVideo) {// 不是设备之间的通话
                RedisClusterService.getJedisCluster().hset(device.getId(), ConstCacheField.VIDEO_OPENID, openId);
                log.info("设备 imei: {} 发起通话请求成功. type = {}, openId = {} ", device.getId(), type, openId);
                add(deviceVideoType, device.getId(), vid, type, openId, device.getPlatform(), isDevToDevVideo, callType);
            } else {
                log.info("设备 imei: {} 向设备 otherImei: {} 发起通话请求成功. type = {}", device.getId(), openId, type);
                add(deviceVideoType, device.getId(), vid, type, openId, device.getPlatform(), isDevToDevVideo, callType);
            }
        }
    }

    /**
     * 配置视频参数（通用方法，从原方法中提取）
     */
    public void configureVideoParameters(Device device, String type, String deviceVideoType,
                                         String openId, String vid, Integer oneCallTime,
                                         JSONObject emitDeviceDemandData, Map<String, Object> tempData,
                                         JSONObject videoConfig, JSONObject updata, String video_identify, String newVideoId, int version) {

        if (device.getModules().contains(ConstVideo.V0SW)) {
            // 声网配置
            configureAgora(device, type, openId, vid, oneCallTime, emitDeviceDemandData, tempData, videoConfig);
        } else if (device.getModules().contains(ConstVideo.V0JF)) {
            // 菊风配置
            configureJuFeng(device, type, openId, vid, oneCallTime, emitDeviceDemandData, tempData, videoConfig, updata, video_identify, newVideoId, version);
        } else if (device.getModules().contains(ConstVideo.V0VM)) {
            // 视通配置
            configureVideoMeet(device, type, openId, vid, oneCallTime, emitDeviceDemandData, tempData, videoConfig);
        }
    }

    /**
     * 配置菊风参数
     */
    private void configureJuFeng(Device device, String type, String openId, String vid,
                                 Integer oneCallTime, JSONObject emitDeviceDemandData,
                                 Map<String, Object> tempData, JSONObject videoConfig, JSONObject updata, String video_identify, String newVideoId, Integer version) {

        if (Const.INT_1.equals(device.getCancelVideoAutoHangUp())) {
            oneCallTime = 60 * 60 * 2;
        }


        String connectionAddress = jfAppConnectionAddress;
        String password = device.getId().substring(device.getId().length() - 6);

        emitDeviceDemandData.put("appkey", jfAppKey);
        emitDeviceDemandData.put("username", "imei-" + device.getId());
        emitDeviceDemandData.put("password", password);
        emitDeviceDemandData.put("type", type);
        emitDeviceDemandData.put("distinguishability_heiger", videoConfig.getInteger("distinguishability_heiger"));
        emitDeviceDemandData.put("distinguishability_width", videoConfig.getInteger("distinguishability_width"));
        //新版本是拼接，老版本是时间
        if (version == 0) {
            emitDeviceDemandData.put("video_id", vid);
        } else if (version == 1) {
            emitDeviceDemandData.put("video_id", newVideoId);
        }

        emitDeviceDemandData.put("limit_time", oneCallTime);
        emitDeviceDemandData.put("wait_time", VIDEO_WAIT_TIME);

        tempData.put("connection_address", jfAppConnectionAddress);
        tempData.put("appkey", jfAppKey);
        if (StringUtil.isNotBlank(openId)) {
            tempData.put("username", openId);
        }
        tempData.put("password", password);
        tempData.put("timestamp", DateUtil.nowTimestampToLong());
        tempData.put("imei", device.getId());

        tempData.put("video_id", vid);
        tempData.put("newVideoId", newVideoId);
        tempData.put("video_type", 4);
        tempData.put("distinguishability_heiger", videoConfig.getInteger("distinguishability_heiger"));
        tempData.put("distinguishability_width", videoConfig.getInteger("distinguishability_width"));
        tempData.put("limit_time", oneCallTime);
        tempData.put("wait_time", VIDEO_WAIT_TIME);

        String shortId = RedisClusterService.getJedisCluster().hget(device.getId(), "shortId");
        //String video_identify = shortId + "_" + DateUtil.dateToStr(new Date(), DateUtil.FORMAT_6);
        if (StringUtils.isNotBlank(shortId)) {

            emitDeviceDemandData.put("app_username", openId);
            emitDeviceDemandData.put("username", device.getId());
            if (version == 0) {
                emitDeviceDemandData.put("video_id", vid);
            } else if (version == 1) {
                emitDeviceDemandData.put("video_id", newVideoId);
            }
            emitDeviceDemandData.put("video_identify", video_identify);

            tempData.put("appkey", jf_SJAppKey);
            tempData.put("video_id", vid);
            tempData.put("device_username", device.getId());
            tempData.put("video_identify", video_identify);

            if ("ASR".equals(device.getPlatform()) || "spreadtrum".equals(device.getPlatform())) {
                connectionAddress = jfWatchConnectionAddress;
                emitDeviceDemandData.put("secret_key", jf_SJASRWatchSecretKey);
            }
        } else {
            tempData.put("appkey", jfAppKey);
            tempData.put("device_username", "imei-" + device.getId());
            if ("ASR".equals(device.getPlatform()) || "spreadtrum".equals(device.getPlatform())) {
                connectionAddress = jfWatchConnectionAddress;
                emitDeviceDemandData.put("secret_key", jfASRWatchSecretKey);
            }
        }

        emitDeviceDemandData.put("connection_address", connectionAddress);
    }

    /**
     * 配置声网参数
     */
    private void configureAgora(Device device, String type, String openId, String vid,
                                Integer oneCallTime, JSONObject emitDeviceDemandData,
                                Map<String, Object> tempData, JSONObject videoConfig) {

        String url = String.format("https://api.agora.io/dev/v1/channel/user/property/%s/%s/%s",
                swAPP_ID, 2222, device.getId());
        String result = HttpReq.sendGet(url, createAuthorization());

        if (StringUtil.isNotBlank(result)) {
            JSONObject resultJson = JSON.parseObject(result).getJSONObject("data");
            if (resultJson != null && resultJson.getBoolean("in_channel") != null
                    && resultJson.getBoolean("in_channel")) {
                throw new RuntimeException("Device already in channel");
            }
        }

        RtcTokenBuilder tokenBuilder = new RtcTokenBuilder();
        String device_token = tokenBuilder.buildTokenWithUid(device.getId(), 1111, swAPP_ID, swAPP_CERTIFICATE);
        String appToken = tokenBuilder.buildTokenWithUid(device.getId(), 2222, swAPP_ID, swAPP_CERTIFICATE);

        // 设备端数据
        emitDeviceDemandData.put("type", type);
        emitDeviceDemandData.put("uid", 1111);
        emitDeviceDemandData.put("app_uid", 2222);
        emitDeviceDemandData.put("token", device_token);
        emitDeviceDemandData.put("distinguishability_heiger", ConstVideo.SW_HEIGER);
        emitDeviceDemandData.put("distinguishability_width", ConstVideo.SW_WIDTH);
        emitDeviceDemandData.put("video_id", vid);
        emitDeviceDemandData.put("limit_time", oneCallTime);
        emitDeviceDemandData.put("wait_time", VIDEO_WAIT_TIME);

        // APP端数据
        tempData.put("timestamp", DateUtil.nowTimestampToLong());
        tempData.put("imei", device.getId());
        tempData.put("video_id", vid);
        tempData.put("video_type", device.getVideoType() == null ? 1 : device.getVideoType());
        tempData.put("app_uid", 2222);
        tempData.put("token", appToken);
        tempData.put("device_uid", 1111);
        tempData.put("device_token", device_token);
        tempData.put("Customer_Certificate", swCUSTOMER_CERTIFICATE);
        tempData.put("Customer_ID", swCUSTOMER_ID);
        tempData.put("appid", swAPP_ID);
        tempData.put("limit_time", oneCallTime);
        tempData.put("wait_time", VIDEO_WAIT_TIME);
        tempData.put("distinguishability_heiger", videoConfig.getInteger("distinguishability_heiger"));
        tempData.put("distinguishability_width", videoConfig.getInteger("distinguishability_width"));
    }

    /**
     * 配置视通参数
     */
    private void configureVideoMeet(Device device, String type, String openId, String vid,
                                    Integer oneCallTime, JSONObject emitDeviceDemandData,
                                    Map<String, Object> tempData, JSONObject videoConfig) {

        if (Const.INT_1.equals(device.getCancelVideoAutoHangUp())) {
            oneCallTime = 60 * 60 * 2;
        }

        String connectionAddress = vmAppConnectionAddress;
        if ("ASR".equals(device.getPlatform()) || "spreadtrum".equals(device.getPlatform())) {
            connectionAddress = vmWatchConnectionAddress;
        }

        String password = device.getId().substring(device.getId().length() - 6);
        emitDeviceDemandData.put("connection_address", connectionAddress);
        emitDeviceDemandData.put("appkey", vmAppKey);
        emitDeviceDemandData.put("username", device.getId());
        emitDeviceDemandData.put("password", password);
        emitDeviceDemandData.put("app_username", openId);
        emitDeviceDemandData.put("type", type);
        emitDeviceDemandData.put("distinguishability_heiger", videoConfig.getInteger("distinguishability_heiger"));
        emitDeviceDemandData.put("distinguishability_width", videoConfig.getInteger("distinguishability_width"));
        emitDeviceDemandData.put("video_id", vid);
        emitDeviceDemandData.put("limit_time", oneCallTime);
        emitDeviceDemandData.put("wait_time", VIDEO_WAIT_TIME);

        tempData.put("connection_address", vmAppConnectionAddress);
        tempData.put("appkey", vmAppKey);
        tempData.put("username", openId);
        tempData.put("password", password);
        tempData.put("timestamp", System.currentTimeMillis() / 1000);
        tempData.put("imei", device.getId());
        tempData.put("device_username", device.getId());
        tempData.put("video_id", vid);
        tempData.put("video_type", 5);
        tempData.put("bitRate", 380);
        tempData.put("FPS", 8);
        tempData.put("distinguishability_heiger", videoConfig.getInteger("distinguishability_heiger"));
        tempData.put("distinguishability_width", videoConfig.getInteger("distinguishability_width"));
        tempData.put("limit_time", oneCallTime);
        tempData.put("wait_time", VIDEO_WAIT_TIME);
    }

    /**
     * 检查通话忙碌状态
     */
    private int checkCallBusyStatus(Device device, String type, String openId) {
        String devVideoOpenId = redisVideoService.get(CacheEnum.VIDEO.getKey() + device.getId());
        String userVideoIMEI = redisVideoService.get(CacheEnum.VIDEO.getKey() + openId);

        // 检查群聊状态
        String devGroupStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + device.getId());
        String userGroupStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + openId);


        if (!ConstKafka.DEVICE_TYPE_DownVideoCallWithWatch.equals(type)) {
            // 设备邀请
            if (StringUtil.isNotBlank(userVideoIMEI) && !userVideoIMEI.equals(device.getId())) {
                redisVideoService.del(CacheEnum.VIDEO.getKey() + device.getId());
                return -4;
            }
            // 检查用户是否在群聊中
            if (StringUtil.isNotBlank(userGroupStatus)) {
                log.info("用户 {} 正在群聊 {} 中，无法接受单聊邀请", openId, userGroupStatus);
                return -4; // 对方正在通话中
            }
        } else {
            // APP邀请
            if (StringUtil.isNotBlank(devVideoOpenId) && !devVideoOpenId.equals(openId)) {
                redisVideoService.del(CacheEnum.VIDEO.getKey() + openId);
                return -4;
            }
            // 检查设备是否在群聊中
            if (StringUtil.isNotBlank(devGroupStatus)) {
                log.info("设备 {} 正在群聊 {} 中，无法接受单聊邀请", device.getId(), devGroupStatus);
                return -4; // 对方正在通话中
            }
        }
        return 0;
    }

    /**
     * 检查在线状态和上课禁用
     */
    private String checkOnlineStatus(Device device, String type, Integer isVideoMonito) {
        String onlineStatus = "1";

        //由APP发起的
        if (ConstKafka.DEVICE_TYPE_DownVideoCallWithWatch.equals(type)) {
            onlineStatus = RedisClusterService.getJedisCluster().hget(device.getId(), ConstCacheField.ONLINE_STATUS);

            if (CollectionUtil.isNotEmpty(device.getDisBanMap()) && isVideoMonito != 1) {
                int day = DateUtil.getWeekOfDate(new Date());
                for (Map.Entry<String, Map<String, Object>> entry : device.getDisBanMap().entrySet()) {
                    Object week = entry.getValue().get("week");
                    Object status = entry.getValue().get("status");
                    if (StringUtil.isNotBlank(status) && "1".equals(status.toString()) && StringUtil.isNotBlank(week)
                            && week.toString().length() > day - 1 && "1".equals(String.valueOf(week.toString().charAt(day - 1)))
                            && StringUtil.isNotBlank(entry.getValue().get("starttime")) && StringUtil.isNotBlank(entry.getValue().get("endedtime"))) {
                        int starttime = Integer.parseInt(entry.getValue().get("starttime").toString().replace(":", ""));
                        int endedtime = Integer.parseInt(entry.getValue().get("endedtime").toString().replace(":", ""));
                        if (DateUtil.getHourAndMinute() >= starttime && DateUtil.getHourAndMinute() <= endedtime) {
                            log.warn("imei:{} 正处于上课禁用状态，无法视频通话", device.getId());
                            return "-1"; // 返回特殊值表示上课禁用
                        }
                    }
                }
            }
        }

        return onlineStatus;
    }

    /**
     * 检查视频限制
     */
    private int checkVideoLimit(Device device, Integer callType, Long nowTime, Integer callInterval) {
        // 553 视频限制 (只有视频进行限制, 语音1 不限制)
        if (Const.INT_1.equals(device.getJFvideolimit553()) && callType != 1) {
            List<String> redisData = redisVideoService.hmget(device.getId(), "lastEndVideoTime", "videoLimitStatus");
            if (redisData != null && redisData.size() > 0) {
                int lastEndVideoTime = 0;
                int videoLimitStatus = 0;
                //上一次通话 结束的 时间点
                if (StringUtil.isNumeric(redisData.get(0))) {
                    lastEndVideoTime = new Double(redisData.get(0)).intValue();
                }
                // 是否限制设备视频通话
                if (StringUtil.isNumeric(redisData.get(1))) {
                    videoLimitStatus = new Double(redisData.get(1)).intValue();
                }

                log.info("imei:{} 上一次的视频Redis缓存:lastEndVideoTime:{} videoLimitStatus:{}", device.getId(), lastEndVideoTime, videoLimitStatus);
                if (nowTime - lastEndVideoTime >= callInterval) {
                    log.info("imei:{} nowTime:{} - lastEndVideoTime:{} >= callInterval:{} 清除视频553限制", device.getId(), nowTime, lastEndVideoTime, callInterval);
                    videoLimitStatus = 0;
                    Map<String, String> tempRedisData = new HashMap<>();
                    tempRedisData.put("videoLimitStatus", "0");
                    tempRedisData.put("rVideoTime", "0");
                    tempRedisData.put("rIntervalVideoTime", "0");
                    redisVideoService.hmset(device.getId(), tempRedisData);
                }
                if (videoLimitStatus == 1) {
                    log.info("imei:{} 553 视频限制了", device.getId());
                    return -3;
                }
            }
        }
        return 0;
    }

    /**
     * 根据设备型号ID 获取对应的视频配置
     *
     * @param modelId
     * @time: 3/1/22 2:59 PM
     * @author: Weichao
     * @version 1.0
     */
    private JSONObject getVideoConfig(String modelId) {
        JSONObject res = new JSONObject();
        //视频配置的变量
        //宽 * 高
        String display_resolution = "120*160";
        Integer one_call_time = LIMIT_ONE_CALL_VIDEO_TIME;
        Integer call_interval = LIMIT_INTERVAL_VIDEO_TIME;
        Integer all_call_time = LIMIT_ALL_VIDEO_TIME;
        if (modelId != null) {
            Models modelsOB = modelsService.findById(modelId);
            if (modelsOB != null) {
                if (modelsOB.getModules().contains(ConstVideo.V0JF)) {
                    //菊风
                    display_resolution = "120*160";
                } else if (modelsOB.getModules().contains(ConstVideo.V0SW)) {
                    //声网
                    display_resolution = ConstVideo.SW_WIDTH + "*" + ConstVideo.SW_HEIGER;
                } else {
                    //视通
                    display_resolution = ConstVideo.DISTINGUISHABILITY_WIDTH + "*" + ConstVideo.DISTINGUISHABILITY_HEIGER;
                }
                if (modelsOB.getVideo_modelid() != null) {
                    VideoModel videoModelOB = videoModelService.findById(modelsOB.getVideo_modelid());
                    if (videoModelOB != null) {
                        if (videoModelOB.getDisplayResolution() != null &&
                                videoModelOB.getDisplayResolution().length() > 0) {
                            display_resolution = videoModelOB.getDisplayResolution();
                        }
                        one_call_time = videoModelOB.getOneCallTime();
                        call_interval = videoModelOB.getCallInterval();
                        all_call_time = videoModelOB.getAllCallTime();
                    }
                }
            }
        }
        res.put("distinguishability_width", display_resolution.split("\\*")[0]);
        res.put("distinguishability_heiger", display_resolution.split("\\*")[1]);
        res.put("one_call_time", one_call_time);
        res.put("call_interval", call_interval);
        res.put("all_call_time", all_call_time);
        return res;
    }

    /**
     * @param imei
     * @param vo
     * @time: 2021/1/15 9:12
     * @author: Rock
     * @version 1.0
     */
    public ResponseMap sendVideo(String imei, SendVideoReqVO vo, Integer isVideoMonito) {
        Device device = deviceService.findById(imei);
        if (device == null || StringUtil.isBlank(device.getModules())) {
            log.error("Device or Modules is null. id={} ", imei);
            return ResponseMap.fail();
        }
        ResponseMap res = ResponseMap.success();
        if (StringUtil.isNotBlank(vo.getType())) {
            if ("getInfo".equals(vo.getType())) {
                if (StringUtil.isNotBlank(device.getNewVideoId())) {
                    boolean isTimeOut = DateUtil.isTimeout(device.getNewVideoId(), VIDEO_WAIT_TIME);
                    log.info("imei = {}, newVideo_id = {}, isTimeout = {}", device.getId(), device.getNewVideoId(), isTimeOut);
                    if (isTimeOut) {
                        log.info("发起视频的时间间隔超过了等待时间,那么就返回无效失败 openid:{} videoId:{}", vo.getOpenid(), device.getNewVideoId());
                        return ResponseMap.success();
                    }
                    DeviceVideo deviceVideo = this.findOne(new Query(Criteria.where("video_id").is(device.getNewVideoId())));
                    if (deviceVideo == null || !vo.getOpenid().equals(deviceVideo.getOpenid())
                            || Const.INT_1.equals(deviceVideo.getStatus())) {
                        return ResponseMap.success();
                    }
                    //该设备的设备类型的ID
                    String modelId = device.getModelId();
                    JSONObject videoConfig = getVideoConfig(modelId);
                    res.put("wait_time", VIDEO_WAIT_TIME);
                    res.put("limit_time", Integer.MAX_VALUE);
                    res.put("", videoConfig.getInteger("distinguishability_heiger"));
                    res.put("distinguishability_width", videoConfig.getInteger("distinguishability_width"));
                    if (StringUtil.isNotBlank(device.getPlatform())) {
                        res.put("platform", device.getPlatform());
                    }
                    if (device.getModules().contains(ConstVideo.V0SW)) {
                        RtcTokenBuilder tokenBuilder = new RtcTokenBuilder();
                        String device_token = tokenBuilder.buildTokenWithUid(device.getId(), 1111, swAPP_ID, swAPP_CERTIFICATE);
                        String appToken = tokenBuilder.buildTokenWithUid(device.getId(), 2222, swAPP_ID, swAPP_CERTIFICATE);
                        res.put("appkey", swAPP_ID);
                        res.put("video_type", 1);
                        res.put("app_uid", 2222);
                        res.put("token", appToken);
                        res.put("device_uid", 1111);
                        res.put("device_token", device_token);
                        res.put("Customer_Certificate", swCUSTOMER_CERTIFICATE);
                        res.put("Customer_ID", swCUSTOMER_ID);
                        return ResponseMap.success(res);
                    } else if (device.getModules().contains(ConstVideo.V0JF)) {
                        res.put("connection_address", jfAppConnectionAddress);
                        res.put("appkey", jfAppKey);
                        res.put("imei", imei);
                        res.put("video_type", 4);
                        res.put("username", vo.getOpenid());
                        res.put("password", vo.getOpenid().substring(vo.getOpenid().length() - 6));
                        String shortId = RedisClusterService.getJedisCluster().hget(device.getId(), "shortId");
                        if (StringUtils.isNotBlank(shortId)) {
                            //三基的短标识设备 --- 菊风逻辑
                            res.put("appkey", jf_SJAppKey);
                        }
                        return ResponseMap.success(res);
                    } else if (device.getModules().contains(ConstVideo.V0VM)) {

                        res.put("connection_address", vmAppConnectionAddress);
                        res.put("appkey", vmAppKey);
                        res.put("imei", imei);
                        res.put("video_type", 5);
                        res.put("username", vo.getOpenid());
                        res.put("password", vo.getOpenid().substring(vo.getOpenid().length() - 6));
                        //视通SDK iOS 的分辨率只支持 352*288 不支持 176*144 的, 后台只下发 176*144 的分辨率 接入的 iOS app 自己写死
                        //码率 /kbps
                        res.put("bitRate", 380);
                        //帧率 /s
                        res.put("FPS", 8);


                        return ResponseMap.success(res);
                    } else {
                        log.error("device.getModules() is null. imei={}", device.getId());
                        return ResponseMap.success();
                    }
                } else {
                    //log.error("device.getNewVideoId() is null. imei={}", device.getId());
                    return ResponseMap.success();
                }
            } else {
                return ResponseMap.fail(ResponseEnum.STATUS403.getCode(), "API.request.video.getVideoInfo");
            }
        }
        String type = ConstKafka.DEVICE_TYPE_DownVideoCallWithWatch;
        String deviceVideoType = "";
        if (device.getModules().contains(ConstVideo.V0VM)) {
            deviceVideoType = "VM";
        } else if (device.getModules().contains(ConstVideo.V0SW)) {
            deviceVideoType = "SW";
        } else if (device.getModules().contains(ConstVideo.V0JF)) {
            deviceVideoType = "JF";
        }
        if (deviceVideoType.length() == 0) {
            return ResponseMap.fail("API.request.video.notVideo");
        }
        JSONObject emitDeviceDemandData = new JSONObject();
        // APP 视频邀请 所以 ident 为 null
        Map<String, Object> tempData = new HashMap<>();
        int code = invitateVideo(device, type, deviceVideoType, vo.getOpenid(), null, isVideoMonito, tempData, null, vo.getCallType(), vo.getIsGroup(), emitDeviceDemandData,
                vo.getVersion());
        switch (code) {
            case -1:
                return ResponseMap.fail(ResponseEnum.STATUS6000.getCode(), ResponseEnum.STATUS6000.getMsg());
            case -2:
                return ResponseMap.fail(ResponseEnum.STATUS403.getCode(), "API.request.video.limitVideoTime");
            case -3:
                return ResponseMap.fail(ResponseEnum.STATUS403.getCode(), "API.request.video.limitVideoTime");
            case -4:
                return ResponseMap.fail(ResponseEnum.STATUS403.getCode(), "API.request.video.callAPPBusy");
            case 1:
                return ResponseMap.fail(ResponseEnum.STATUS403.getCode(), "通话时长不足，请点击“续费”按钮进行续费");
            case 2:
                return ResponseMap.fail(ResponseEnum.STATUS403.getCode(), "API.request.notVideo");
            case 3:
                return ResponseMap.fail(ResponseEnum.STATUS403.getCode(), "API.request.video.callDeviceBusy");
            case 4:
                return ResponseMap.fail(ResponseEnum.STATUS403.getCode(), "API.request.video.notFindCallContact");
            case 0:
                return ResponseMap.fail(ResponseEnum.STATUS5001.getCode(), ResponseEnum.STATUS5001.getMsg());
//            //设备 <==> 设备 的通话
//            case 998:
//                return ResponseMap.fail(ResponseEnum.STATUS5001.getCode(), ResponseEnum.STATUS5001.getMsg());
            case 999:
                // APP发起通话成功
                if (tempData.size() > 0) {
                    res.putAll(tempData);
                    res.put("call_type", vo.getCallType());
                    res.put("deviceVideoType", deviceVideoType);
                    //res.put("video_id", tempData.get("newVideoId"));
                    return ResponseMap.success(res);
                }
            default:
                log.error(" invitateVideo code is not fonud. imei={}. code={}. vo={}", device.getId(), code, vo.toString());
        }
        return ResponseMap.fail(ResponseEnum.STATUS5001.getCode(), ResponseEnum.STATUS5001.getMsg());
    }

    /**
     * 声网创建凭证,于服务端使用
     */
    private String createAuthorization() {
        String content = swCUSTOMER_ID + ":" + swCUSTOMER_CERTIFICATE;
        byte[] encodedAuth = Base64.encodeBase64(content.getBytes(Charset.forName("US-ASCII")));
        String authHeader = "Basic " + new String(encodedAuth);
        return authHeader;
    }

    /**
     * 上传通话时长
     *
     * @param imei
     * @param videoId
     * @param duration
     * @param waitDuration
     * @param type
     * @time: 2021/3/17 11:48
     * @author: Rock
     * @version 1.0
     */
    public ResponseMap updateDeviceVideo(String imei, String videoId, Integer duration, Integer waitDuration, String type, String openId) {
        Device device = deviceService.findById(imei);
        //todo 判断是群聊单聊，群聊用群聊标识查，单聊用单聊标识查
        DeviceVideo deviceVideo = this.findOne(new Query(Criteria.where("video_id").is(device.getVideoIdentify())));
        if (videoId.endsWith("_group") || videoId.equals(device.getGroupVideoIdentify())) {
            deviceVideo = this.findOne(new Query(Criteria.where("video_id").is(device.getGroupVideoIdentify())));
        }


        if (deviceVideo == null || device == null) {
            log.error("DeviceVideo or Device is null. _id={}, _id = {}", videoId, imei);
            return ResponseMap.fail();
        }
        Update updateVideo = new Update();
        if (StringUtil.isBlank(deviceVideo.getEndOfType())) {
            deviceVideo.setEndOfType(type);
            updateVideo.set("endOfType", type);
        }
        if (waitDuration == null) {
            waitDuration = 0;
        }
//              //=====================群聊处理逻辑======================
        // 检查是否是群聊
        boolean isGroupCall = false;
        String groupId = null;
        ChatGroup chatGroup = null;

        // 检查Redis中的通话状态，判断是否是群聊
        String callStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + imei);
        if (StringUtil.isNotBlank(callStatus)) {
            isGroupCall = true;
            groupId = callStatus;
            chatGroup = chatGroupService.findById(groupId);
        }

        if (isGroupCall && chatGroup != null) {
            // 调用群聊处理方法
            handleGroupCallEnd(device, chatGroup, videoId, type, duration, deviceVideo, openId);

        } else {
            //原有的单聊处理逻辑
            if (StringUtil.isNotBlank(deviceVideo.getOpenid())) {
                if (deviceVideo.getOpenid().equals(redisVideoService.get(CacheEnum.VIDEO.getKey() + imei))) {
                    //如果这个通话对象不是自己 就不操作,是自己就清除标识 ,保证下次可以拨打 accountRedis 中删除
                    redisVideoService.del(CacheEnum.VIDEO.getKey() + imei);
                }
                if (imei.equals(redisVideoService.get(CacheEnum.VIDEO.getKey() + deviceVideo.getOpenid()))) {
                    //如果这个通话对象不是自己 就不操作,是自己就清除标识 ,保证下次可以拨打 accountRedis 中删除
                    redisVideoService.del(CacheEnum.VIDEO.getKey() + deviceVideo.getOpenid());
                }
            }
        }
        log.info("imei:{} (方式:{})上传通话时长 ", device.getId(), type);
        if (duration > 0) {
            if (deviceVideo.getStatus() == null || Const.INT_0.equals(deviceVideo.getStatus())
                    && !videoId.equals(device.getVideoId())) {
                //如果有553 的限制 且不是语音通话
                if (Const.INT_1.equals(device.getJFvideolimit553()) && deviceVideo.getCallType() != 1) {
//                    Map<String, String> data = redisAccountService.hgetAll(imei);
                    List<String> dataList = redisVideoService.hmget(
                            imei,
                            "rVideoTime",
                            "rVideoCount",
                            "RecentlyVideoCount",
                            "lastEndVideoTime"
                    );
                    String lastEndVideoTime = "0";
                    if (dataList != null) {
                        lastEndVideoTime = dataList.get(3);
                    }
                    if (StringUtil.isNumeric(lastEndVideoTime) &&
                            (System.currentTimeMillis() / 1000.0) - Long.valueOf(lastEndVideoTime) < 2) {
                        //如果2个上传的视频时间和间隔小于2s 那么忽略不计,可能是重复上报
                        log.info("imei:{} 如果2个上传的视频时间和间隔小于2s 那么忽略不计,可能是重复上报", imei);
                        return ResponseMap.success();
                    }
                    //真实的通话时间
                    long trueDurationTime = System.currentTimeMillis() / 1000 - deviceVideo.getStartTime().longValue();
                    log.info("imei:{} {}上报此次视频(video_id:{}) 的通话时长,等待时间:{} 上传通话时间:{} 后台计算的真实通话时间:{} ",
                            device.getId(), type, videoId, waitDuration, duration, trueDurationTime);
                    // 启用限制的 视频通话总分钟数 最近视频通话累计时间 /秒
                    int allCallTime = LIMIT_ALL_VIDEO_TIME;
                    if (StringUtil.isNotBlank(device.getVideoModelId())) {
                        VideoModel videoModel = videoModelService.findById(device.getVideoModelId());
                        if (videoModel != null && videoModel.getAllCallTime() != null) {
                            allCallTime = videoModel.getAllCallTime();
                        }
                    }
                    if (trueDurationTime > allCallTime) {
                        Map<String, String> temp = new HashMap<>();
                        temp.put("lastEndVideoTime", String.valueOf(System.currentTimeMillis() / 1000));
                        temp.put("rVideoTime", "0");
                        temp.put("rIntervalVideoTime", "0");
                        temp.put("rVideocount", "1");
                        temp.put("videoLimitStatus", "1");
                        redisVideoService.hmset(imei, temp);
                        log.info("imei:{} 真实通话时间:{} > 配置允许的最近视频通话累计时间:{}", device.getId(), trueDurationTime, allCallTime);
                    } else if (dataList != null && dataList.size() > 0) {
                        // 最近通话累计时间
                        int rVideoTime = StringUtil.isNumeric(dataList.get(0)) ? Integer.parseInt(dataList.get(0)) : 0;
                        // 最近通话累计次数
                        int rVideoCount = StringUtil.isNumeric(dataList.get(1)) ? Integer.parseInt(dataList.get(1)) : 0;
                        Map<String, String> tempData = new HashMap<>();
                        tempData.put("rVideoCount", String.valueOf(rVideoCount + 1));
                        if (rVideoTime + trueDurationTime > allCallTime) {
                            tempData.put("videoLimitStatus", "1");
                            tempData.put("rVideoTime", "0");
                            tempData.put("rIntervalVideoTime", "0");
                            tempData.put("RecentlyVideoCount", "0");
                            log.info("imei:{} 最近通话累计次数(rVideoCount:{}) 最近通话累计时间(rVideoTime:{})+最新一次真实通话时间(trueDurationTime:{}) > 总限制时间({}) 进行553限制",
                                    imei, rVideoCount, rVideoTime, trueDurationTime, allCallTime);
                        } else {
                            //最近视频数
                            int RecentlyVideoCount = StringUtil.isNumeric(dataList.get(2)) ? Integer.parseInt(dataList.get(2)) : 0;
                            tempData.put("rVideoTime", String.valueOf(rVideoTime + trueDurationTime));
                            tempData.put("RecentlyVideoCount", String.valueOf(RecentlyVideoCount + 1));
                            log.info("imei:{} 最近通话累计时间(rVideoTime:{})+最新一次真实通话时间(trueDurationTime:{}) < 总限制时间({}) 累加通话累计时间",
                                    imei, rVideoTime, trueDurationTime, allCallTime);
                        }
                        //更新 上一次通话 结束的 时间点
                        tempData.put("lastEndVideoTime", String.valueOf(System.currentTimeMillis() / 1000));
                        redisVideoService.hmset(imei, tempData);
                    } else {
                        log.info("imei:{} 以上情况都不是,去除限制", device.getId());
                        Map<String, String> temp = new HashMap<>();
                        temp.put("lastEndVideoTime", String.valueOf(System.currentTimeMillis() / 1000));
                        temp.put("rVideoTime", String.valueOf(trueDurationTime));
                        temp.put("rIntervalVideoTime", "0");
                        temp.put("rVideocount", "0");
                        temp.put("videoLimitStatus", "0");
                        temp.put("RecentlyVideoCount", "0");
                        redisVideoService.hmset(imei, temp);
                    }
                }

                //结束视频时间 - 发起视频时间 = 真正的视频时间  duration(不包含拨打时等待的时间)
                //视频的剩余分钟数 (免费可不用理这参数,但是还会累计)
                int videoEndtime = device.getVideoEndtime() == null ? ConstVideo.DEFAULT_VIDEO_TIME : device.getVideoEndtime();
                updateVideo.set("origin_time", videoEndtime);
                if (duration >= LIMIT_ONE_CALL_VIDEO_TIME && (device.getCancelVideoAutoHangUp() == null
                        || Const.INT_0.equals(device.getCancelVideoAutoHangUp()))) {
                    duration = LIMIT_ONE_CALL_VIDEO_TIME - 1;
                }
                videoEndtime -= duration / 60 + 1;
                updateVideo.set("last_time", videoEndtime);
                updateVideo.set("end_time", DateUtil.nowTimestampToDouble());
                updateVideo.set("wait_duration", waitDuration);
                updateVideo.set("duration", duration);
                updateVideo.set("type", type);
                updateVideo.set("status", 1);

                Update updateDevice = new Update();
                updateDevice.set("video_id", videoId);
                updateDevice.set("video_endtime", videoEndtime);
                if (StringUtil.isNotBlank(device.getNewVideoId())) {
                    updateDevice.unset("newVideo_id");
                }
                deviceService.updateById(imei, updateDevice);
                //如果 设备拨=>设备挂,有完整的通话时长上传,需要清除去掉未接通记录
                if (deviceVideo.getRequestType() != null && !ConstKafka.DEVICE_TYPE_DownVideoCallWithWatch.equals(deviceVideo.getRequestType())
                        && "device".equals(deviceVideo.getEndOfType())) {
                    deviceLastMissedVideoCallService.delByOpenidAndVideoId(deviceVideo.getOpenid(), videoId);
                    //  deviceLastMissedVideoCallService.delById(deviceVideo.getOpenid());

                }
            } else {
                log.info("device video_id={}. deviceVideo video_id={}. deviceVideo status={}",
                        device.getVideoId(), videoId, deviceVideo.getStatus());
            }
        }

        this.updateById(new ObjectId(deviceVideo.getId()), updateVideo);
        return ResponseMap.success();
    }


    private void handleGroupCallEnd(Device device, ChatGroup chatGroup, String videoId, String type, Integer duration, DeviceVideo deviceVideo, String openid) {
        if (chatGroup == null) {
            return;
        }

        String groupId = chatGroup.getId();
        String imei = device.getId();

        // 1. 清除发送者的通话状态
        if (type.equals("device")) {
            redisVideoService.del(CacheEnum.GROUP_VIDEO.getKey() + imei);
            log.info("群组 {} 中设备 {} 上传通话时间，清除设备通话状态", groupId, imei);
        } else {
            String userVideoStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + openid);
            if (StringUtil.isNotBlank(userVideoStatus) && userVideoStatus.equals(groupId)) {
                redisVideoService.del(CacheEnum.GROUP_VIDEO.getKey() + openid);
                log.info("群组 {} 中用户 {} 上传通话时间，清除用户通话状态", groupId, openid);
            }
        }

        // 3. 检查并清理群组状态
        checkAndCleanGroupCallStatus(chatGroup, groupId);


    }

    /**
     * 检查并清理群组通话状态
     */
    public Map<String, Object> checkAndCleanGroupCallStatus(ChatGroup chatGroup, String groupId) {
        int remainingMembers = 0;
        List<String> remainingMemberIds = new ArrayList<>();

        // 检查所有设备
        if (CollectionUtil.isNotEmpty(chatGroup.getDeviceMap())) {
            for (String deviceImei : chatGroup.getDeviceMap().keySet()) {
                String devStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + deviceImei);
                if (StringUtil.isNotBlank(devStatus) && devStatus.equals(groupId)) {
                    remainingMembers++;
                    remainingMemberIds.add("设备:" + deviceImei);
                }
            }
        }

        // 检查所有APP成员
        if (CollectionUtil.isNotEmpty(chatGroup.getMemberMap())) {
            for (String memberOpenId : chatGroup.getMemberMap().keySet()) {
                String memberStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + memberOpenId);
                if (StringUtil.isNotBlank(memberStatus) && memberStatus.equals(groupId)) {
                    remainingMembers++;
                    remainingMemberIds.add("APP:" + memberOpenId);
                }
            }
        }

        log.info("群组 {} 当前剩余 {} 个成员在通话中: {}", groupId, remainingMembers, remainingMemberIds);

        // 准备返回值
        Map<String, Object> result = new HashMap<>();
        result.put("count", remainingMembers);

        // 如果剩余成员数<=1，结束整个群组通话
        if (remainingMembers <= 1) {
            // 清除最后一个成员的状态
            if (remainingMembers == 1) {
                String lastMember = remainingMemberIds.get(0);
                if (lastMember.startsWith("设备:")) {
                    String imei = lastMember.substring(3);
                    redisVideoService.del(CacheEnum.GROUP_VIDEO.getKey() + imei);
                    result.put("lastMemberID", imei);
                    result.put("lastMember", "设备:");
                    //如果最后一个人是设备，下发通知
                } else if (lastMember.startsWith("APP:")) {
                    String openId = lastMember.substring(4);
                    redisVideoService.del(CacheEnum.GROUP_VIDEO.getKey() + openId);
                    result.put("lastMember", "APP:");
                    result.put("lastMemberID", openId);
                }
            }
            // 清除群组通话状态
            redisVideoService.del(CacheEnum.GROUP_VIDEO.getKey() + groupId);
            log.info("群组 {} 所有成员已退出通话，清除群组通话状态", groupId);
        }
        return result;
    }


    /**
     * 清除指定群组的所有通话状态
     *
     * @param chatGroup 群组对象
     * @param groupId   群组ID
     */
    public void clearGroupCallStatus(ChatGroup chatGroup, String groupId) {
        // 清除所有设备的通话状态
        if (CollectionUtil.isNotEmpty(chatGroup.getDeviceMap())) {
            for (String deviceImei : chatGroup.getDeviceMap().keySet()) {
                String deviceStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + deviceImei);
                if (StringUtil.isNotBlank(deviceStatus) && deviceStatus.equals(groupId)) {
                    redisVideoService.del(CacheEnum.GROUP_VIDEO.getKey() + deviceImei);
                    log.info("清除群组 {} 设备 {} 的通话状态", groupId, deviceImei);
                }
            }
        }

        // 清除所有APP成员的通话状态
        if (CollectionUtil.isNotEmpty(chatGroup.getMemberMap())) {
            for (String memberOpenId : chatGroup.getMemberMap().keySet()) {
                String memberStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + memberOpenId);
                if (StringUtil.isNotBlank(memberStatus) && memberStatus.equals(groupId)) {
                    redisVideoService.del(CacheEnum.GROUP_VIDEO.getKey() + memberOpenId);
                    log.info("清除群组 {} 成员 {} 的通话状态", groupId, memberOpenId);
                }
            }
        }

        // 清除群组本身的通话状态
        redisVideoService.del(CacheEnum.GROUP_VIDEO.getKey() + groupId);
        log.info("清除群组 {} 的通话状态", groupId);
    }

    /**
     * 拒绝视频通话邀请
     *
     * @param imei
     * @param vo
     * @time: 2021/1/18 11:16
     * @author: Rock
     * @version 1.0
     */


    public ResponseMap rejectDeviceVideo(String imei, RejectDeviceVideoReqVO vo, Integer count) {
        Device device = deviceService.findById(imei);
        if (device == null) {
            log.error("Device is null. _id = {}", imei);
            return ResponseMap.fail(ResponseEnum.STATUS2001.getCode(), ResponseEnum.STATUS2001.getMsg(), imei);
        }
        // DeviceVideo deviceVideo = this.findOne(new Query(Criteria.where("video_id").is(device.getNewVideoId())));
        DeviceVideo deviceVideo = this.findOne(new Query(Criteria.where("video_id").is(device.getVideoIdentify())));
        // 检查是否是群聊
        boolean isGroupCall = false;
        String groupId = null;
        String vid = null;
        String shortId = RedisClusterService.getJedisCluster().hget(device.getId(), "shortId");
        //todo 判断vo传过来的时间戳和device存的是不是一样或者后缀是不是group
        if (vo.getVideo_id().endsWith("_group") || vo.getVideo_id().equals(device.getGroupVideoIdentify())) {
            isGroupCall = true;
            vid = shortId + "_" + "group";
            deviceVideo = this.findOne(new Query(Criteria.where("video_id").is(device.getGroupVideoIdentify())));
        }
        // String callStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + imei);
//        if (StringUtil.isNotBlank(callStatus)) {
//            isGroupCall = true;
//            groupId = callStatus;
//            vid = shortId + "_" + "group";
//            deviceVideo = this.findOne(new Query(Criteria.where("video_id").is(device.getGroupVideoIdentify())));
//        }
        ChatGroup chatGroup = chatGroupService.getChatGroupByDevice(device);

        if (deviceVideo == null) {
            log.error("DeviceVideo is null. video_id = {}", vo.getVideo_id());
            return ResponseMap.fail(ResponseEnum.STATUS2001.getCode(), "视频记录不存在");
        }
        Integer callType = deviceVideo.getCallType();

        if (isGroupCall) {
            // 群聊处理逻辑
            //  ChatGroup chatGroup = chatGroupService.findById(groupId);
            if (chatGroup != null) {
                // 清除APP用户的通话状态
                String userVideoStatus = redisVideoService.get(CacheEnum.GROUP_VIDEO.getKey() + vo.getOpenid());
                if (StringUtil.isNotBlank(userVideoStatus) && userVideoStatus.equals(groupId)) {
                    redisVideoService.del(CacheEnum.GROUP_VIDEO.getKey() + vo.getOpenid());
                    log.info("群组 {} 中用户 {} 挂断通话，清除用户通话状态", groupId, vo.getOpenid());
                }
                // 创建公共的 userData
                JSONObject userData = new JSONObject();
                userData.put("time", DateUtil.nowTimestampToDouble());
                // userData.put("video_id", device.getNewVideoId());
                userData.put("video_id", device.getGroupVideoIdentify());
                userData.put("newVideoId", vid);
                //userData.put("video_identify", device.getNewVideoId());
                userData.put("video_identify", device.getGroupVideoIdentify());
                userData.put("isGroup", 1);
                userData.put("type", "reject");
                userData.put("count", count);
                userData.put("call_type", callType);
                userData.put("imei", device.getId());
                userData.put("name", device.getName());


                JSONObject data = new JSONObject();
                data.put("time", DateUtil.nowTimestampToDouble());
                //给设备的要用拼接好的video_id
                data.put("video_id", vid);
                data.put("isGroup", 1);      // 表示群聊
                data.put("count", count);
                data.put("call_type", callType);
                data.put("type", "reject");
                data.put("imei", device.getId());
                data.put("name", device.getName());
                data.put("user_openid", device.getId());
                // data.put("user_openid", vo.getOpenid());
                //count为0，说明是取消，删除所有人通话状态和群组通话状态
                // 这里是只剩下最后一个人，并且是设备才会推送给设备
                if (count != null) {
                    //redisVideoService.del(CacheEnum.GROUP_VIDEO.getKey() + "*");
                    // 清除当前群组的所有通话状态
                    clearGroupCallStatus(chatGroup, groupId);
                    log.info("群组 {} 所有成员已退出通话，清除群组通话状态", groupId);
                    data.put("hangupType", 1);  // 表示取消通话
                    kafkaService.emitDeviceDemand(imei,
                            ConstKafka.DEVICE_TYPE_DownAPPHangUp, data, vo.getOpenid());
                    log.info("群组 {} 群成员已退出通话，发送APP取消通话指令", groupId);

                } else {
                    //计算当前通话人数，如果只有一个人清除群组通话状态
                    Map<String, Object> groupCallStatus = checkAndCleanGroupCallStatus(chatGroup, groupId);
                    // 如果房间人数一个人，才下发指令，并且那个人是设备
                    String lastMember = (String) groupCallStatus.get("lastMember");
                    Integer count1 = (Integer) groupCallStatus.get("count");
                    if (count1 == 1 && lastMember != null && lastMember.equals("设备:")) {
                        data.put("hangupType", 1);
                        kafkaService.emitDeviceDemand(imei,
                                ConstKafka.DEVICE_TYPE_DownAPPHangUp, data, vo.getOpenid());
                        log.info("发送挂断设备通话指令", device.getId());

                        JSONObject rejectData = new JSONObject();
                        rejectData.put("type", ConstKafka.ACCOUNT_MESSAGE);
                        if (CollectionUtil.isNotEmpty(chatGroup.getMemberMap())) {
                            for (String memberOpenId : chatGroup.getMemberMap().keySet()) {
                                userData.put("openid", memberOpenId);
                                rejectData.put("message", userData);
                                kafkaService.sendPushToAccount(ConstKafka.ACCOUNT_TYPE_3, memberOpenId, rejectData);
                                log.info("群组成员 {} 推送给app未接通通话", memberOpenId);
                            }
                        }
                    } else if (count1 == 1 && lastMember != null && lastMember.equals("APP:")) {
                        JSONObject rejectData = new JSONObject();
                        rejectData.put("type", ConstKafka.ACCOUNT_MESSAGE);
                        //给最后一个用户和未进来的用户
                        if (CollectionUtil.isNotEmpty(chatGroup.getMemberMap())) {
                            for (String memberOpenId : chatGroup.getMemberMap().keySet()) {
                                userData.put("openid", memberOpenId);
                                rejectData.put("message", userData);
                                kafkaService.sendPushToAccount(ConstKafka.ACCOUNT_TYPE_3, memberOpenId, rejectData);
                                log.info("群组成员 {} 推送给app未接通挂断通话", memberOpenId);
                            }
                        }
                        data.put("hangupType", 1);  // 挂断设备
                        kafkaService.emitDeviceDemand(imei,
                                ConstKafka.DEVICE_TYPE_DownAPPHangUp, data, vo.getOpenid());
                        log.info("群组 {} 设备未接通的情况，给设备推挂断");
                    }
                }

            }
        } else {
            if (StringUtil.isNotBlank(device.getNewVideoId())) {
                if (StringUtil.isBlank(vo.getOpenid())) {
                    //     DeviceVideo deviceVideo = this.findOne(new Query(Criteria.where("video_id").is(vo.getVideo_id())));
                    if (deviceVideo != null) {
                        vo.setOpenid(deviceVideo.getOpenid());
                        //只记录一下第一个结束方, 后面的都忽略
                        if (StringUtil.isBlank(deviceVideo.getEndOfType())) {
                            this.updateById(new ObjectId(deviceVideo.getId()), new Update().set("endOfType", "APP"));
                        }
                    }
                }

                String openid = redisVideoService.get(CacheEnum.VIDEO.getKey() + imei);
                if (StringUtil.isNotBlank(openid) && openid.equals(vo.getOpenid())) {
                    redisVideoService.del(CacheEnum.VIDEO.getKey() + imei);
                    log.info("platform={} imei={} 与APP视频挂断(APP调用的接口) 清除通话状态video:imei openid={}", device.getPlatform(), imei, openid);
                }
                String userVideoIMEI = redisVideoService.get(CacheEnum.VIDEO.getKey() + vo.getOpenid());
                if (StringUtil.isNotBlank(userVideoIMEI) && userVideoIMEI.equals(imei)) {
                    redisVideoService.del(CacheEnum.VIDEO.getKey() + vo.getOpenid());
                    log.info("platform={} imei={} 与APP视频挂断(APP调用的接口) 清除通话状态video:openid openid={}", device.getPlatform(), imei, openid);
                }
                JSONObject data = new JSONObject();
                data.put("time", DateUtil.nowTimestampToDouble());

                vid = shortId + "_" + openid;
                data.put("video_id", vid);
                data.put("isGroup", 0);
                data.put("call_type", callType);
                kafkaService.emitDeviceDemand(imei, ConstKafka.DEVICE_TYPE_DownAPPHangUp, data, openid);
            }
            Update update = new Update();
            update.unset("newVideo_id");
            deviceService.updateById(imei, update);
        }
        return ResponseMap.success();
    }

    public void joinVideoRoom(String openid, String imei) {
        Device device = deviceService.findById(imei);
        if (device != null) {
            ChatGroup chatGroup = chatGroupService.getChatGroupByDevice(device);
            redisVideoService.setex(CacheEnum.VIDEO.getKey() + openid, CacheEnum.GROUP_VIDEO.getExpire(), chatGroup.getId());
        }
    }
}